# -*- coding: utf-8 -*-
"""
文章服务层
目的：封装文章相关的业务逻辑，提供统一的文章操作接口
职责：处理文章发布、编辑、查询、删除等核心业务逻辑
"""
from datetime import datetime
from typing import Optional, Dict, Any
from sqlalchemy.orm import Session
from app.models.article import Article
from app.models.category import Category
from app.config.database import SessionLocal
from app.utils.response import Result
from app.dto.article_dto import CreateArticleRequest, GetArticlesListRequest
from app.utils.datetime_utils import get_db_now


class ArticleService:
    """
    文章业务逻辑服务类
    目的：提供文章相关的所有业务操作，将业务逻辑与控制器层解耦
    职责：文章的增删改查、数据验证、业务规则处理
    使用场景：所有文章相关的业务操作都通过此服务类处理
    """

    @staticmethod
    def create_article(request: CreateArticleRequest) -> Result:
        """
        创建新文章
        目的：处理文章发布的业务逻辑，包括数据验证和存储
        功能：验证必填字段、验证分类是否存在、创建文章记录并返回结果

        Args:
            request (CreateArticleRequest): 创建文章请求对象，封装了所有创建文章所需的参数

        Returns:
            Result: 统一响应结果对象
                - 成功时返回创建的文章信息
                - 失败时返回错误消息
        """
        # 创建数据库会话对象，用于执行数据库操作
        db: Session = SessionLocal()

        try:
            # 验证必填字段是否为空
            if not all([request.title, request.content, request.summary, request.tags]):
                return Result.validation_error('文章标题、内容、摘要和标签均为必填项')

            # 验证分类是否存在且状态为启用
            category = db.query(Category).filter(
                Category.id == request.category_id,
                Category.status == 1  # 只能使用启用状态的分类
            ).first()

            if not category:
                return Result.validation_error('分类不存在或已被禁用')

            # 创建文章对象，初始化所有必要字段
            article = Article(
                title=request.title.strip(),  # 去除标题首尾空格
                content=request.content,
                summary=request.summary.strip(),  # 去除摘要首尾空格
                author_id=request.author_id,
                category_id=request.category_id,
                tags=request.tags.strip(),  # 去除标签首尾空格
                cover_image=request.cover_image,
                location_id=request.location_id,
                status=request.status,
                published_at=get_db_now() if request.status == 1 else None  # 已发布状态设置发布时间
            )

            # 将文章对象添加到数据库会话
            db.add(article)
            # 提交事务，保存到数据库
            db.commit()
            # 刷新对象，获取数据库生成的ID等字段
            db.refresh(article)

            return Result.success(data=article.to_dict(), message='文章发布成功')

        except Exception as e:
            # 发生异常时回滚事务，保证数据一致性
            db.rollback()
            # 返回错误信息
            return Result.internal_error(f'文章发布失败：{str(e)}')

        finally:
            # 关闭数据库会话，释放连接资源
            db.close()

    @staticmethod
    def get_article_by_id(article_id: int, increment_view: bool = False) -> Result:
        """
        根据ID获取文章详情
        目的：查询指定ID的文章信息，支持增加浏览次数
        功能：从数据库查询文章并可选地更新浏览计数

        Args:
            article_id (int): 文章ID
            increment_view (bool): 是否增加浏览次数，默认False

        Returns:
            Result: 统一响应结果对象
                - 成功时返回文章完整信息
                - 失败时返回404错误
        """
        # 创建数据库会话对象
        db: Session = SessionLocal()

        try:
            # 根据ID查询文章
            article = db.query(Article).filter(Article.id == article_id).first()

            # 如果文章不存在，返回404错误
            if not article:
                return Result.not_found('文章不存在')

            # 如果需要增加浏览次数
            if increment_view:
                article.view_count += 1
                db.commit()
                db.refresh(article)

            return Result.success(data=article.to_dict(), message='获取文章成功')

        except Exception as e:
            return Result.internal_error(f'获取文章失败：{str(e)}')

        finally:
            # 关闭数据库会话
            db.close()

    @staticmethod
    def get_categories_list(status: Optional[int] = 1) -> Result:
        """
        获取分类列表
        目的：查询所有分类信息，用于文章发布时选择分类
        功能：根据状态筛选分类，按排序字段排序

        Args:
            status (Optional[int]): 分类状态筛选，1-启用，0-禁用，None-全部

        Returns:
            Result: 统一响应结果对象
                - 成功时返回分类列表
                - 失败时返回错误消息
        """
        # 创建数据库会话对象
        db: Session = SessionLocal()

        try:
            # 构建查询对象
            query = db.query(Category)

            # 如果指定了状态，添加状态筛选条件
            if status is not None:
                query = query.filter(Category.status == status)

            # 按排序字段和ID排序，确保分类顺序固定
            categories = query.order_by(Category.sort_order, Category.id).all()

            # 将分类对象列表转换为字典列表
            categories_list = [category.to_dict() for category in categories]

            return Result.success(data=categories_list, message='获取分类列表成功')

        except Exception as e:
            return Result.internal_error(f'获取分类列表失败：{str(e)}')

        finally:
            # 关闭数据库会话
            db.close()

    @staticmethod
    def get_articles_list(request: GetArticlesListRequest) -> Result:
        """
        获取文章列表（分页）
        目的：查询文章列表，支持分页、筛选和排序功能
        功能：根据多种条件筛选文章，按置顶和发布时间排序，返回分页数据

        Args:
            request (GetArticlesListRequest): 获取文章列表请求对象，封装了所有查询参数

        Returns:
            Result: 统一响应结果对象
                - 成功时返回包含文章列表和分页信息的数据
                - 失败时返回错误消息
        """
        # 创建数据库会话对象
        db: Session = SessionLocal()

        try:
            # 验证分页参数的有效性
            page = request.page if request.page >= 1 else 1
            page_size = request.page_size if request.page_size >= 1 else 10
            if page_size > 100:  # 限制单页最大数量，防止查询过大数据
                page_size = 100

            # 构建基础查询对象
            query = db.query(Article)

            # 根据分类ID筛选
            if request.category_id is not None:
                query = query.filter(Article.category_id == request.category_id)

            # 根据文章状态筛选
            if request.status is not None:
                query = query.filter(Article.status == request.status)

            # 根据作者ID筛选
            if request.author_id is not None:
                query = query.filter(Article.author_id == request.author_id)

            # 根据置顶状态筛选
            if request.is_top is not None:
                query = query.filter(Article.is_top == request.is_top)

            # 关键词搜索，同时搜索标题和摘要字段
            if request.keyword:
                search_pattern = f"%{request.keyword}%"  # 构建模糊搜索模式
                query = query.filter(
                    (Article.title.like(search_pattern)) |
                    (Article.summary.like(search_pattern))
                )

            # 查询总记录数，用于计算总页数
            total = query.count()

            # 排序：按创建时间倒序（新文章在前）
            query = query.order_by(Article.is_top.desc(), Article.created_at.desc())

            # 计算分页偏移量
            offset = (page - 1) * page_size

            # 执行分页查询
            articles = query.offset(offset).limit(page_size).all()

            # 将文章对象列表转换为字典列表，不包含完整内容以提升性能
            articles_list = [article.to_dict(include_content=False) for article in articles]

            # 计算总页数
            total_pages = (total + page_size - 1) // page_size

            return Result.success(
                data={
                    'articles': articles_list,
                    'total': total,
                    'page': page,
                    'page_size': page_size,
                    'total_pages': total_pages
                },
                message='获取文章列表成功'
            )

        except Exception as e:
            return Result.internal_error(f'获取文章列表失败：{str(e)}')

        finally:
            # 关闭数据库会话
            db.close()

    @staticmethod
    def update_article(article_id: int, author_id: int, title: Optional[str] = None,
                      content: Optional[str] = None, summary: Optional[str] = None,
                      category_id: Optional[int] = None, tags: Optional[str] = None,
                      cover_image: Optional[str] = None, location_id: Optional[int] = None,
                      status: Optional[int] = None, is_top: Optional[int] = None) -> Result:
        """
        更新文章信息
        目的：修改指定文章的信息，支持部分字段更新
        功能：验证文章存在性、权限检查、验证更新数据、更新文章记录

        Args:
            article_id (int): 文章ID，必填
            author_id (int): 作者ID，用于权限验证
            title (Optional[str]): 文章标题，可选更新
            content (Optional[str]): 文章内容，可选更新
            summary (Optional[str]): 文章摘要，可选更新
            category_id (Optional[int]): 分类ID，可选更新
            tags (Optional[str]): 标签，可选更新
            cover_image (Optional[str]): 封面图片，可选更新
            location_id (Optional[int]): 地理位置ID，可选更新
            status (Optional[int]): 文章状态，可选更新
            is_top (Optional[int]): 是否置顶，可选更新

        Returns:
            Result: 统一响应结果对象
                - 成功时返回更新后的文章信息
                - 失败时返回错误消息
        """
        # 创建数据库会话对象
        db: Session = SessionLocal()

        try:
            # 查询要更新的文章
            article = db.query(Article).filter(Article.id == article_id).first()

            # 如果文章不存在，返回404错误
            if not article:
                return Result.not_found('文章不存在')

            # 权限检查：只有作者本人才能编辑文章
            if article.author_id != author_id:
                return Result.forbidden('无权编辑此文章')

            # 如果要更新标题，验证标题有效性
            if title is not None:
                title = title.strip()
                if not title:
                    return Result.validation_error('文章标题不能为空')
                article.title = title

            # 如果要更新内容，验证内容有效性
            if content is not None:
                if not content.strip():
                    return Result.validation_error('文章内容不能为空')
                article.content = content

            # 如果要更新摘要，验证摘要有效性
            if summary is not None:
                summary = summary.strip()
                if not summary:
                    return Result.validation_error('文章摘要不能为空')
                article.summary = summary

            # 如果要更新分类，验证分类是否存在
            if category_id is not None:
                category = db.query(Category).filter(
                    Category.id == category_id,
                    Category.status == 1
                ).first()

                if not category:
                    return Result.validation_error('分类不存在或已被禁用')

                article.category_id = category_id

            # 如果要更新标签，验证标签有效性
            if tags is not None:
                tags = tags.strip()
                if not tags:
                    return Result.validation_error('文章标签不能为空')
                article.tags = tags

            # 更新封面图片
            if cover_image is not None:
                article.cover_image = cover_image

            # 更新地理位置
            if location_id is not None:
                article.location_id = location_id

            # 更新状态
            if status is not None:
                if status not in [0, 1, 2]:
                    return Result.validation_error('文章状态参数错误，必须为0(草稿)、1(已发布)或2(下线)')

                # 如果状态改为已发布，且之前不是已发布状态，更新发布时间
                if status == 1 and article.status != 1:
                    article.published_at = get_db_now()

                article.status = status

            # 更新置顶状态
            if is_top is not None:
                if is_top not in [0, 1]:
                    return Result.validation_error('置顶状态参数错误，必须为0(不置顶)或1(置顶)')
                article.is_top = is_top

            # 提交事务
            db.commit()
            # 刷新对象
            db.refresh(article)

            return Result.success(data=article.to_dict(), message='文章更新成功')

        except Exception as e:
            # 发生异常时回滚事务
            db.rollback()
            return Result.internal_error(f'文章更新失败：{str(e)}')

        finally:
            # 关闭数据库会话
            db.close()

    @staticmethod
    def delete_article(article_id: int, author_id: int, force: bool = False) -> Result:
        """
        删除文章
        目的：删除指定的文章，支持软删除和强制删除
        功能：验证文章存在性、权限检查、删除文章

        Args:
            article_id (int): 文章ID，必填
            author_id (int): 作者ID，用于权限验证
            force (bool): 是否强制删除，默认False
                - False: 软删除，将状态设置为下线(status=2)
                - True: 物理删除，直接从数据库删除记录

        Returns:
            Result: 统一响应结果对象
                - 成功时返回成功消息
                - 失败时返回错误消息
        """
        # 创建数据库会话对象
        db: Session = SessionLocal()

        try:
            # 查询要删除的文章
            article = db.query(Article).filter(Article.id == article_id).first()

            # 如果文章不存在，返回404错误
            if not article:
                return Result.not_found('文章不存在')

            # 权限检查：只有作者本人才能删除文章
            if article.author_id != author_id:
                return Result.forbidden('无权删除此文章')

            # 根据删除方式处理
            if force:
                # 强制删除：物理删除
                # 注意：实际生产环境中，建议保留文章数据，仅做软删除
                db.delete(article)
                message = '文章删除成功'
            else:
                # 软删除：设置状态为下线
                article.status = 2
                message = '文章已下线'

            # 提交事务
            db.commit()

            return Result.success(message=message)

        except Exception as e:
            # 发生异常时回滚事务
            db.rollback()
            return Result.internal_error(f'文章删除失败：{str(e)}')

        finally:
            # 关闭数据库会话
            db.close()
